#include "civparser.h"

#include <buola/gl.h>
#include <buola/scene/cmesh.h>
#include <buola/scene/cvertexdata.h>
#include <buola/scene/cindexdata.h>
#include <buola/scene/cmeshbuilder.h>

#include <buola/io/cfile.h>

#include <buola/zz.h>
#include <buola/zz/zrule.h>
#include <buola/zz/if.h>
#include <buola/zz/zsymbols.h>
#include <buola/zz/zchar.h>
#include <buola/zz/zlexeme.h>
#include <buola/zz/zreal.h>
#include <buola/zz/operators.h>
#include <buola/zz/repeat.h>
#include <buola/zz/zexpectop.h>

namespace buola { namespace scene { namespace import {

enum ENodeNames
{
    NODE_SEPARATOR,
    NODE_SHAPEHINTS,
    NODE_INDEXEDTRIANGLESTRIPSET,
    NODE_VERTEXPROPERTY,
    NODE_MATRIXTRANSFORM,
    NODE_MATERIAL,
    NODE_MATERIALBINDING,
    NODE_NORMAL,
    NODE_NORMALBINDING,
    NODE_COORDINATE3,
    NODE_INDEXEDFACESET,
    NODE_INFO,
    NODE_LIGHTMODEL,
    NODE_TEXCOORDBINDING,
    NODE_TEXCOORD2
};

enum EPropertyNames
{
    PROPERTY_VERTEXORDERING,
    PROPERTY_SHAPETYPE,
    PROPERTY_VERTEXPROPERTY,
    PROPERTY_VERTEX,
    PROPERTY_NORMAL,
    PROPERTY_MATRIX,
    PROPERTY_AMBIENTCOLOR,
    PROPERTY_DIFFUSECOLOR,
    PROPERTY_SPECULARCOLOR,
    PROPERTY_EMISSIVECOLOR,
    PROPERTY_SHININESS,
    PROPERTY_TRANSPARENCY,
    PROPERTY_VALUE,
    PROPERTY_VECTOR,
    PROPERTY_POINT,
    PROPERTY_MATERIALINDEX,
    PROPERTY_COORDINDEX,
    PROPERTY_NORMALINDEX,
    PROPERTY_TEXCOORDINDEX,
    PROPERTY_MATERIALBINDING,
    PROPERTY_NORMALBINDING,
    PROPERTY_STRING,
    PROPERTY_MODEL,
    PROPERTY_FACETYPE,
    PROPERTY_CREASEANGLE
};

struct CIVParser::ANodeStart
{
    ANodeStart(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()(uint32_t pNode) const
    {
        switch(pNode)
        {
        case NODE_NORMAL:
            mParser.mCurrentVector=&mParser.mNormals;
            break;
        case NODE_COORDINATE3:
            mParser.mCurrentVector=&mParser.mPositions;
            break;
        case NODE_TEXCOORD2:
            mParser.mCurrentVector=nullptr;
            break;
        case NODE_MATERIAL:
            mParser.mMaterialMode=true;
            break;
        }
        mParser.mNodeStack.push(pNode);
    }
    
    CIVParser &mParser;
};
    
struct CIVParser::ANodeEnd
{
    ANodeEnd(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()() const
    {
        uint32_t lNode=mParser.mNodeStack.top();
        mParser.mNodeStack.pop();
        
        switch(lNode)
        {
        case NODE_MATERIAL:
            mParser.mMaterialMode=false;
            break;
        }
    }
    
    CIVParser &mParser;
};
    
struct CIVParser::APropertyStart
{
    APropertyStart(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()(uint32_t pProperty) const
    {
        mParser.mCurrentProperty=pProperty;
        
        switch(pProperty)
        {
        case PROPERTY_MATERIALINDEX:
            mParser.mCurrentIntVector=&mParser.mMaterialIndices;
            break;
        case PROPERTY_NORMALINDEX:
            mParser.mCurrentIntVector=&mParser.mNormalIndices;
            break;
        case PROPERTY_COORDINDEX:
            mParser.mCurrentIntVector=&mParser.mCoordIndices;
            break;
        case PROPERTY_TEXCOORDINDEX:
            mParser.mCurrentIntVector=nullptr;
            break;
        case PROPERTY_NORMAL:
            mParser.mCurrentVector=&mParser.mNormals;
            break;
        case PROPERTY_VERTEX:
            mParser.mCurrentVector=&mParser.mPositions;
            break;
        }
    }

    CIVParser &mParser;
};

struct CIVParser::A3DVector
{
    A3DVector(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()(const std::array<double,3> &pVector) const
    {
        if(mParser.mMaterialMode)
        {
            std::vector<mat::CVec4d> *lVector=nullptr;
            if(mParser.mCurrentProperty==PROPERTY_DIFFUSECOLOR)
                lVector=&mParser.mMaterials;
            if(!lVector) return;
            
            lVector->emplace_back(pVector[0],pVector[1],pVector[2],1.0);
        }
        else
        {
            if(!mParser.mCurrentVector) return;

            mParser.mCurrentVector->emplace_back(pVector[0],pVector[1],pVector[2]);
        }
    }

    CIVParser &mParser;
};

struct CIVParser::A4DVector
{
    A4DVector(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()(const std::array<double,4> &pVector) const
    {
        if(mParser.mMaterialMode)
        {
            std::vector<mat::CVec4d> *lVector=nullptr;
            if(mParser.mCurrentProperty==PROPERTY_DIFFUSECOLOR)
                lVector=&mParser.mMaterials;
            if(!lVector) return;
            
            lVector->emplace_back(pVector[0],pVector[1],pVector[2],pVector[3]);
        }
        else
        {
            msg_warn() << "4d vector found in wrong place\n";
        }
    }

    CIVParser &mParser;
};

struct CIVParser::AInteger
{
    AInteger(CIVParser &pParser)
        :   mParser(pParser)
    {}

    void operator()(int pIndex) const
    {
        if(!mParser.mCurrentIntVector) return;

        mParser.mCurrentIntVector->push_back(pIndex);
    }

    CIVParser &mParser;
};

CIVParser::CIVParser()
{

}

CIVParser::~CIVParser()
{
}

PMesh CIVParser::Parse(const io::CURI &pURL,double pScale)
{
    ///\todo replace next six lines with a single one which reads a whole file
    io::CFile lFile(pURL);
    std::size_t lFileSize=lFile.Size();

    io::PStream lS=io::open(pURL,io::EMode::READ);
    std::vector<char> lBuf(lFileSize);
    if(lS->Read((uint8_t*)lBuf.data(),lFileSize).Count()!=lFileSize)
        throw XIO("can't read iv file");

    auto lSkipper=zz::space();
    typedef zz::ZRule<const char*,UUnused,decltype(lSkipper)> TRule;
    typedef zz::ZSymbols<char,uint32_t> TSymbols;
    
    TSymbols lNodeNames;
    lNodeNames.Add("Separator",NODE_SEPARATOR);
    lNodeNames.Add("ShapeHints",NODE_SHAPEHINTS);
    lNodeNames.Add("IndexedTriangleStripSet",NODE_INDEXEDTRIANGLESTRIPSET);
    lNodeNames.Add("VertexProperty",NODE_VERTEXPROPERTY);
    lNodeNames.Add("MatrixTransform",NODE_MATRIXTRANSFORM);
    lNodeNames.Add("Material",NODE_MATERIAL);
    lNodeNames.Add("MaterialBinding",NODE_MATERIALBINDING);
    lNodeNames.Add("Normal",NODE_NORMAL);
    lNodeNames.Add("NormalBinding",NODE_NORMALBINDING);
    lNodeNames.Add("Coordinate3",NODE_COORDINATE3);
    lNodeNames.Add("IndexedFaceSet",NODE_INDEXEDFACESET);
    lNodeNames.Add("Info",NODE_INFO);
    lNodeNames.Add("LightModel",NODE_LIGHTMODEL);
    lNodeNames.Add("TextureCoordinateBinding",NODE_NORMALBINDING);
    lNodeNames.Add("TextureCoordinate2",NODE_TEXCOORD2);

    TSymbols lPropertyNames;
    lPropertyNames.Add("vertexOrdering",PROPERTY_VERTEXORDERING);
    lPropertyNames.Add("shapeType",PROPERTY_SHAPETYPE);
    lPropertyNames.Add("vertexProperty",PROPERTY_VERTEXPROPERTY);
    lPropertyNames.Add("vertex",PROPERTY_VERTEX);
    lPropertyNames.Add("normal",PROPERTY_NORMAL);
    lPropertyNames.Add("matrix",PROPERTY_MATRIX);
    lPropertyNames.Add("ambientColor",PROPERTY_AMBIENTCOLOR);
    lPropertyNames.Add("diffuseColor",PROPERTY_DIFFUSECOLOR);
    lPropertyNames.Add("specularColor",PROPERTY_SPECULARCOLOR);
    lPropertyNames.Add("emissiveColor",PROPERTY_EMISSIVECOLOR);
    lPropertyNames.Add("shininess",PROPERTY_SHININESS);
    lPropertyNames.Add("transparency",PROPERTY_TRANSPARENCY);
    lPropertyNames.Add("value",PROPERTY_VALUE);
    lPropertyNames.Add("vector",PROPERTY_VECTOR);
    lPropertyNames.Add("point",PROPERTY_POINT);
    lPropertyNames.Add("materialIndex",PROPERTY_MATERIALINDEX);
    lPropertyNames.Add("coordIndex",PROPERTY_COORDINDEX);
    lPropertyNames.Add("textureCoordIndex",PROPERTY_TEXCOORDINDEX);
    lPropertyNames.Add("normalIndex",PROPERTY_NORMALINDEX);
    lPropertyNames.Add("materialBinding",PROPERTY_MATERIALBINDING);
    lPropertyNames.Add("normalBinding",PROPERTY_NORMALBINDING);
    lPropertyNames.Add("string",PROPERTY_STRING);
    lPropertyNames.Add("model",PROPERTY_MODEL);
    lPropertyNames.Add("faceType",PROPERTY_FACETYPE);
    lPropertyNames.Add("creaseAngle",PROPERTY_CREASEANGLE);
    
    ANodeStart aNodeStart(*this);
    ANodeEnd aNodeEnd(*this);
    APropertyStart aPropertyStart(*this);
    A3DVector a3DVector(*this);
    A4DVector a4DVector(*this);
    AInteger aInteger(*this);
                  
    TRule rNode;
    auto rComment=zz::chr('#') >> zz::lexeme(+(zz::chr()-zz::chr('\n')));
    auto rVector3=zz::repeat<3>(zz::dbl())++[a3DVector];
    auto rVector4=zz::repeat<4>(zz::dbl())++[a4DVector];
    auto rInteger=zz::int32()++[aInteger];
    auto rDouble=zz::dbl();
    auto rString=zz::lexeme(zz::chr('"') >> *(zz::chr()-zz::chr('"')) >> zz::chr());
    auto rArrayElement=rVector4|rVector3|rInteger|rDouble;
    auto rArray=zz::chr('[') >> +(rArrayElement >> *(zz::chr(',') >> rArrayElement)) >> -zz::chr(',') >> zz::chr(']');
    auto rName=zz::lexeme(+(zz::chr()-zz::space()))-zz::chr('}');
    auto rMatrix=+zz::dbl();
    auto rAttributeValue=rNode | rArray | rMatrix | rString | rName;
    auto rProperty=lPropertyNames++[aPropertyStart] >> rAttributeValue;
    rNode=lNodeNames++[aNodeStart] >> zz::expect(zz::chr('{')) 
                                   >> zz::expect(*(rComment|rNode|rProperty)) 
                                   >> zz::expect(zz::chr('}')++[aNodeEnd]);
    auto rFile=*(rComment|rNode);

    const char *lBegin=lBuf.data();
    const char *lIt=lBegin;

    mMaterialMode=false;
    mCurrentVector=nullptr;
    mCurrentIntVector=nullptr;
    mCurrentMaterialVector=nullptr;
    
    try
    {
        zz::phrase_parse(lIt,lBegin+lFileSize,rFile,lSkipper);

        if(lIt!=lBegin+lFileSize)
        {
            msg_warn() << "couldn't parse whole iv file " << pURL << "\n"
                       << "parsed " << lIt-lBegin << " bytes\n";
        }
    }
    catch(zz::XExpect &pX)
    {
        msg_warn() << "expectation failed parsing iv file " << pURL << "\n"
                   << "parsed " << pX.Where<const char*>()-lBegin << " bytes\n";
    }

    std::vector<int> lPosVertices;
    std::vector<int> lNormalVertices;
    std::vector<int> lMaterialVertices;
    std::vector<int> lFaces;

    lFaces.push_back(0);

    std::vector<int> lSubMesh;
    
    for(int i=0;i<mCoordIndices.size();i++)
    {
        if(mCoordIndices[i]==-1)
        {
            lSubMesh.push_back(lFaces.size()-1);
            lFaces.push_back(lPosVertices.size());
        }
        else
        {
            if(mNormalIndices.size())
                lNormalVertices.push_back(mNormalIndices[i]);
            else 
                lNormalVertices.push_back(lPosVertices.size());
            lPosVertices.push_back(mCoordIndices[i]);
        }
    }

    CMeshBuilder lBuilder;

    lBuilder.SetPosition(mPositions);
    lBuilder.SetNormal(mNormals);

    lBuilder.SetVertices(lPosVertices,lNormalVertices);
    lBuilder.SetFaces(lFaces);
    if(mMaterials.size()&&mMaterialIndices.size())
        lBuilder.SetMaterial(mMaterials,mMaterialIndices);
    
    lBuilder.AddSubMesh("default_mesh",lSubMesh,EPrimitive::TRIANGLE_STRIP);
    lBuilder.Scale(pScale);

    return lBuilder.BuildMesh();
}

/*namespace import*/ } /*namespace scene*/ } /*namespace buola*/ }
